/*
* Copyright (c) 2012 Kyle Hayward
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "Precompiled.h"
#include "BufferStream.h"

using namespace System;
using namespace System::IO;


namespace OptixDotNet
{
	BufferStream::BufferStream( Stream^ source )
	{
		if( !source->CanRead )
			throw gcnew NotSupportedException( "Optix Error: BufferStream: source Stream does not support reading." );

		mPosition = 0;
		mSize = source->Length;

		mBuffer = new char[ mSize ];
		memset( mBuffer, 0, mSize );

		mCanRead = true;
		mCanWrite = true;
		mOwnData = true;

		if( mOwnData )
		{
			GC::AddMemoryPressure( mSize );
		}

		array<unsigned char>^ buffer = gcnew array<unsigned char>( 4096 );

		int num = 0;
		while( ( num = source->Read( buffer, 0, buffer->Length) ) != 0 )
		{
			Write( buffer, 0, num );
		}

		source->Close();
		buffer = nullptr;
		mCanWrite = false;
	}

	BufferStream::BufferStream( void* buffer, System::Int64 sizeInBytes, bool canRead, bool canWrite, bool ownData )
	{
		mBuffer = (char*)buffer;
		mSize = sizeInBytes;

		mPosition = 0;

		mCanRead = canRead;
		mCanWrite = canWrite;
		mOwnData = ownData;

		if( ownData )
		{
			GC::AddMemoryPressure( sizeInBytes );
		}
	}

	BufferStream::!BufferStream()
	{
		if( mOwnData && mBuffer != 0 )
		{
			delete[] mBuffer;
			GC::RemoveMemoryPressure( mSize );
			mBuffer = 0;
		}
	}

	generic<typename T> where T : value class
	T BufferStream::Read()
	{
		T t;
		Read( t );

		return t;
	}

	generic<typename T> where T : value class
	void BufferStream::Read( T% data )
	{
		if( !mCanRead )
			throw gcnew NotSupportedException( "Optix Error: BufferStream: Cannot read from non Output Optix buffers" );

		int elemSize = sizeof( T );
		int sizeInBytes = elemSize;

		// internal checks
		if( mPosition + sizeInBytes > mSize )
			throw gcnew EndOfStreamException();

		pin_ptr<T> ptr = &data;
		memcpy( ptr, mBuffer + mPosition, sizeInBytes );
		mPosition += sizeInBytes;
	}

	generic<typename T> where T : value class
	int BufferStream::ReadRange( array<T>^ buffer, int offset, int numElems )
	{
		if( !mCanRead )
			throw gcnew NotSupportedException( "Optix Error: BufferStream: Cannot read from non Output Optix buffers" );

		Int64 elemSize = sizeof( T );
		Int64 sizeInBytes = numElems * elemSize;

		//perform some validation steps
		if( buffer == nullptr )
			throw gcnew ArgumentNullException( "buffer" );

		if( offset < 0 )
			throw gcnew ArgumentOutOfRangeException( "offset" );

		if( sizeInBytes < 0 || ( offset + sizeInBytes > buffer->Length ) )
			throw gcnew ArgumentOutOfRangeException( "numElems" );

		Int64 validSize = System::Math::Min( mSize - mPosition, sizeInBytes );

		pin_ptr<T> ptr = &buffer[offset];
		memcpy( ptr, mBuffer + mPosition, (size_t)validSize );
		mPosition += validSize;

		return (int)validSize;
	}

	generic<typename T> where T : value class
	void BufferStream::Write( T data )
	{
		if( !mCanWrite )
			throw gcnew NotSupportedException( "Optix Error: BufferStream: Cannot write to non Input Optix buffers" );

		int elemSize = sizeof( T );
		int sizeInBytes = elemSize;

		// internal checks
		if( mPosition + sizeInBytes > mSize )
			throw gcnew EndOfStreamException();

		pin_ptr<T> ptr = &data;
		memcpy( mBuffer + mPosition, ptr, sizeInBytes );
		mPosition += sizeInBytes;
	}

	generic<typename T> where T : value class
	void BufferStream::WriteRange( array<T>^ buffer, int offset, int numElems )
	{
		if( !mCanWrite )
			throw gcnew NotSupportedException( "Optix Error: BufferStream: Cannot write to non Input Optix buffers" );

		int elemSize = sizeof( T );
		int sizeInBytes = numElems * elemSize;

		// buffer checks
		if( buffer == nullptr )
			throw gcnew ArgumentNullException( "buffer" );

		if( offset < 0 )
			throw gcnew ArgumentOutOfRangeException( "offset" );

		if( sizeInBytes < 0 || ( offset + numElems > buffer->Length ) )
			throw gcnew ArgumentOutOfRangeException( "numElems" );

		// internal checks
		if( mPosition + sizeInBytes > mSize )
			throw gcnew EndOfStreamException();

		pin_ptr<T> ptr = &buffer[offset];
		memcpy( mBuffer + mPosition, ptr, sizeInBytes );
		mPosition += sizeInBytes;
	}

	Int64 BufferStream::Seek( Int64 offset, SeekOrigin origin )
	{
		Int64 pos = 0;

		switch( origin )
		{
			case SeekOrigin::Begin:
				pos = offset;
				break;

			case SeekOrigin::Current:
				pos = mPosition + offset;
				break;

			case SeekOrigin::End:
				pos = mSize - offset;
				break;
		}

		if( pos < 0 )
			throw gcnew InvalidOperationException("Cannot seek beyond the beginning of the stream.");
		if( pos > mSize )
			throw gcnew InvalidOperationException("Cannot seek beyond the end of the stream.");

		mPosition = pos;
		return mPosition;
	}

	bool BufferStream::Save( String^ path )
	{
		if( !mCanRead )
			throw gcnew NotSupportedException( "Optix Error: BufferStream: does not support reading." );

		array<unsigned char>^ buffer = gcnew array<unsigned char>( 4096 );
		FileStream^ stream = File::OpenWrite( path );

		int num = 0;
		while( ( num = Read( buffer, 0, buffer->Length) ) != 0 )
		{
			stream->Write( buffer, 0, num );
		}
		
		stream->Close();
		buffer = nullptr;
		return true;
	}
}